package com.sky.aspect;


import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面类，实现公共字段自动填充处理逻辑
 */
@Aspect //声明是一个切面
@Component//也是bean,也交给spring管理
@Slf4j
public class AutoFillAspect {


    /**
     * 切入点
     */
    //切面类需要定义 切入点 和 通知   *(这个包下的所有的类)(*所有的方法) ((..)匹配所有的参数类型) 还要满足，这个方法加入了@AutoFill注解，才会切入
    //切点是一个具体的位置，通常是方法调用时的某一时刻，AOP逻辑（即横切关注点）将在这些位置被应用。
    //* 表示任意返回类型，
    // com.sky.mapper.*.*(..) 表示在 com.sky.mapper 包下的任意子包中的任意类的任意方法。
    // *代表任意类，*代表任意方法名，(..) 表示方法参数可以是任意类型和数量。
    //@annotation()指定了方法必须被 @AutoFill 注解标记
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")//要对那些类的那些方法实施拦截
    public void autoFillPointCut(){}

    /**
     * 前置通知，在通知中为公共字段赋值
     */
    //切入后，要干什么呢：
    // (需要用前置通知，因为在执行insert和update方法之前，就需要为里面的公共字段附上值)
    @Before("autoFillPointCut()")
    //要指定切入点，当匹配到切入点的表达式的时候，就会执行这个通知
    public void autoFill(JoinPoint joinPoint){
        //连接点joinPoint，通过连接点，就可以知道，
        // 当前哪个方法被拦截到了，以及被拦截到的这个方法的参数是什么样子的
        log.info("切面。。。。开始进行公共字段自动填充。。。");

        //获取到当前被拦截的方法上的数据库操作类型，
        // 例如mapper层里的接口注解了@AutoFill(value = OperationType.INSERT)//让切面对这个insert方法进行公共字段的填充
//        Signature signature1 = joinPoint.getSignature();//因为当前被拦截到的是一个方法，所以需要转型，转成MethodSignature（子接口）
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的AutoFill注解对象
        OperationType operationType = autoFill.value();//通过注解，获得数据库的操作类型

        //获取到当前被拦截的方法的参数。。实体对象，
        // 例如mapper层里void insert(Employee employee);里的employee
        Object[] args = joinPoint.getArgs();//获得所有参数
        log.info("当前被拦截到的方法：{}",signature.getName());
        log.info("joinPoint是：{}",joinPoint);
        log.info("当前被拦截到的方法参数值：{}",args);
        //防止出现空指针
        if(args == null || args.length == 0){
            //没有参数，没必要执行了
            return;
        }
        // （args[0]获取的是第一个参数，有多个参数时，需要约定把Mapper.java里的方法的传的参数(实体对象)employee放在第一个位置）
        Object entity = args[0];
        //因为这个实体类型是不确定的，所以要写Object
        log.info("当前被拦截到的方法的第一个参数是：{}",entity);
        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型，为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
        //如果是插入操作，有 4 个公共字段需要赋值，create/update.Time，create/update.User
            try {
//                Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
//                Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
//                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
//                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);

//适用于某些有CreatedUser和CreatedTime和UpdateUser和UpdateTime其中某个或者某几个的属性
//                Method[] methods = entity.getClass().getDeclaredMethods();//可以获得所有的方法
//                for (Method method : methods) {
//                    if(method.getName().equals(AutoFillConstant.SET_CREATE_TIME)){
//                        method.invoke(entity ,now);
//                    }else if(method.getName().equals(AutoFillConstant.SET_CREATE_USER)){
//                        method.invoke(entity ,currentId);
//                    }else if(method.getName().equals(AutoFillConstant.SET_UPDATE_TIME)){
//                        method.invoke(entity ,now);
//                    }else if(method.getName().equals(AutoFillConstant.SET_UPDATE_USER)){
//                        method.invoke(entity ,currentId);
//                    }
//                }
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性来赋值
                setCreateTime.invoke(entity ,now);
                setCreateUser.invoke(entity ,currentId);
                setUpdateTime.invoke(entity ,now);
                setUpdateUser.invoke(entity ,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }else if(operationType == OperationType.UPDATE){
        // 如果是修改操作，有 2 个公共字段需要赋值，update.Time，update.User
            try {
//                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
//                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性来赋值
                setUpdateTime.invoke(entity ,now);
                setUpdateUser.invoke(entity ,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}
